﻿//#define NO_SERVER

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Xml.Linq;
using PlayBox.DataClasses;
using System.Security.Cryptography.X509Certificates;

namespace PlayBox
{
    /// <summary>
    /// Stream und Metadaten eines Downloads
    /// </summary>
    class DownloadInfo
    {
        /// <summary>
        /// Der Daten-Stream des Downloads
        /// </summary>
        public System.IO.Stream ContentStream { get; set; }

        /// <summary>
        /// Die Größe des Downloads
        /// </summary>
        public long ContentLength { get; set; }
    }

    /// <summary>
    /// Serververbindung
    /// </summary>
    class ServerConnection
    {
        #region Static
        
        private static ServerConnection _current = null;

        /// <summary>
        /// Zuletzt erstellte Verbindung
        /// </summary>
        public static ServerConnection Current
        {
            get
            {
                if (_current == null)
                    throw new Exception("ServerConnection needs to be created first.");

                return _current;
            }
        }

        /// <summary>
        /// Erstellt neue Instanz
        /// </summary>
        /// <param name="baseServerAddress">Serveradresse</param>
        /// <returns></returns>
        public static ServerConnection Create (String baseServerAddress)
        {
            _current = new ServerConnection(baseServerAddress);
            return _current;
        }

        #endregion

        /// <summary>
        /// Serveradresse
        /// </summary>
        public String BaseServerAddress { get; set; }

        /// <summary>
        /// Benutzername
        /// </summary>
        public String Username { get; set; }

        /// <summary>
        /// Passwort
        /// </summary>
        public String Password { get; set; }


        private Dictionary<String, bool> _acceptedCertificates;

        /// <summary>
        /// Erstellt neue Verbindung
        /// </summary>
        /// <param name="baseServerAddress">Serveradresse</param>
        public ServerConnection (String baseServerAddress)
        {
            this.BaseServerAddress = baseServerAddress;
            Username = "";
            Password = "";

            _acceptedCertificates = new Dictionary<string, bool>();
        }

        /// <summary>
        /// Erstellt Standard Webrequest
        /// </summary>
        /// <param name="uri">Adresse</param>
        /// <returns></returns>
        private WebRequest CreateWebRequest (String uri)
        {
            return CreateWebRequest(uri, false);
        }

        /// <summary>
        /// Server Certificate Überprüfung, fragt User
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="certification"></param>
        /// <param name="chain"></param>
        /// <param name="sslPolicyErrors"></param>
        /// <returns></returns>
        public bool CertificateAcceptAskUser(object sender, System.Security.Cryptography.X509Certificates.X509Certificate certification, System.Security.Cryptography.X509Certificates.X509Chain chain, System.Net.Security.SslPolicyErrors sslPolicyErrors)
        {
            // Keine Fehler?
            if (sslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
                return true;

            // Gibts schon?
            if (_acceptedCertificates.ContainsKey(certification.Subject))
                return _acceptedCertificates[certification.Subject];

            // Benutzer fragen
            var res = System.Windows.Forms.MessageBox.Show(
                "Error validating certificate:\n" + sslPolicyErrors.ToString() + "\n\n" +
                "Do you want to accept the following server certificate?\n\n" + 
                    certification.Subject + 
                    "\nFingerprint: " + BitConverter.ToString(certification.GetCertHash()), 
                "Server certificate", 
                System.Windows.Forms.MessageBoxButtons.YesNo, 
                System.Windows.Forms.MessageBoxIcon.Warning);
            
            bool accept = (res == System.Windows.Forms.DialogResult.Yes);

            // Speichern
            _acceptedCertificates.Add(certification.Subject, accept);

            return accept;
        }


        /// <summary>
        /// Erstellt Standard Webrequest
        /// </summary>
        /// <param name="uri">Adresse</param>
        /// <param name="useAuth">Gibt an, ob Anmeldedaten mitgeschickt werden</param>
        /// <returns></returns>
        private WebRequest CreateWebRequest (String uri, Boolean useAuth)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.BaseServerAddress + uri);
            request.Method = "GET";

            // Client Certificates hinzufügen
            try 
	        {
                X509Certificate2 cert = new X509Certificate2("Certificates/" + Properties.Settings.Default.BoxName + ".p12", "1234");
                request.ClientCertificates.Add(cert);
	        }
	        catch (Exception)
	        {
	        }
            
            // Server Certificate Validation Callback
            ServicePointManager.ServerCertificateValidationCallback +=
                new System.Net.Security.RemoteCertificateValidationCallback(CertificateAcceptAskUser);
            

            // Credentials schicken?
            if (useAuth)
            {
                if (Username.Length == 0 || Password.Length == 0)
                {
                    // Einloggen probieren
                    var login = new Login();
                    login.Username = Username;
                    login.ShowDialog();

                    // Nochmal
                    if (Username.Length == 0 || Password.Length == 0)
                        throw new Exception("Username and/or Passwort empty");
                }
                
                NetworkCredential credential = new NetworkCredential(Username, Password);
                request.Credentials = credential;
            }


            return request;
        }

        /// <summary>
        /// Sendet und verarbeitet einen Request, der eine XML Antwort schickt
        /// </summary>
        /// <param name="request">Request</param>
        /// <returns></returns>
        private XElement ProcessXMLWebRequest (WebRequest request)
        {
#if NO_SERVER

            // Requested Resource, abschneiden bei BaseUri Länge
            var resource = request.RequestUri.AbsoluteUri.Substring (this.BaseServerAddress.Length);
            int pos = resource.IndexOf("?");
            if (pos != -1)
                resource = resource.Substring(0, pos);

            String content = "";

            switch (resource)
            {
                case "/user/register":
                    //content = "<fault>Benutzername bereits vorhanden</fault>";
                    //break;

                case "/user/change_password":
                case "/shop/buy":
                    content = "<response>ok</response>";
                    break;

                case "/shop/music":
                    content = "<response><song><id>M1</id><title>Testlied</title><artist>Tester</artist><price>0</price></song><song><id>M2</id><title>Testlied 2</title><artist>Tester 2</artist><price>10</price></song></response>";
                    break;
            }

#else
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();

            if (response.StatusCode != HttpStatusCode.OK)
                throw new Exception("Statuscode: " + response.StatusCode.ToString());

            if (response.ContentType != "text/xml")
                throw new Exception("Expected Content Type text/xml. Instead received " + response.ContentType);

            var reader = new System.IO.StreamReader(response.GetResponseStream());
            var content = reader.ReadToEnd();
#endif

            XElement xml = XElement.Parse(content);

            // Überprüfe nach keinen Inhalt
            if (!xml.HasElements && xml.Value.Length == 0)
            {
                throw new Exception("No content");
            }

            // Überprüfe nach Fault
            if (xml.Value.ToLower().StartsWith("[fault]"))
            {
                // Inhalt holen
                String fault = xml.Value;

                throw new Exception(fault);
            }

            return xml;
        }

        /// <summary>
        /// Anfrage zum Registrieren eines neuen Benutzers
        /// </summary>
        /// <param name="username">Benutzername</param>
        /// <param name="hash">Hash des Passworts</param>
        /// <param name="salt">Salt</param>
        /// <returns></returns>
        public bool RegisterUser (String username, String hash, String salt)
        {
            String uri = "/user/register?username=" + username + "&hash=" + hash.ToLower() + "&salt=" + salt.ToLower();
            var request = CreateWebRequest(uri);

            // ----------------------------
            // OK: <response>ok</response>
            // ----------------------------

            XElement response = ProcessXMLWebRequest(request);

            return response.Name.LocalName.ToLower() == "response" 
                && response.Value.ToUpper() == "OK";
        }

        /// <summary>
        /// Sendet Anfrage zum Ändern des Passworts des aktuell angemeldeten Benutzers
        /// </summary>
        /// <param name="hash">Hash des Passworts</param>
        /// <param name="salt">Salt</param>
        /// <returns></returns>
        public bool ChangePassword (String hash, String salt)
        {
            String uri = "/user/change_password?username=" + Username + "&hash=" + hash.ToLower() + "&salt=" + salt.ToLower();
            var request = CreateWebRequest(uri, true);

            // ----------------------------
            // OK: <response>ok</response>
            // ----------------------------

            XElement response = ProcessXMLWebRequest(request);

            return response.Name.LocalName.ToLower() == "response"
                && response.Value.ToUpper() == "OK";
        }

        /// <summary>
        /// Liefert alle Lieder im Shop
        /// </summary>
        /// <returns></returns>
        public List<Music> GetShopMusic ()
        {
            String uri = "/shop/music";
            var request = CreateWebRequest(uri);

            // ----------------------------
            // OK: <response><song>...</song></response>
            // ----------------------------

            XElement response = ProcessXMLWebRequest(request);

            List<Music> list = new List<Music>();

            foreach (var item in response.Descendants ("song"))
            {
                Music m = new Music();
                m.ID = item.Element("id").Value;
                m.Title = item.Element("title").Value;
                m.Artist = item.Element("artist").Value;

                Decimal price;
                if (Decimal.TryParse(item.Element("price").Value, out price))
                    m.Price = price;
                else
                    m.Price = -1;

                list.Add(m);
            }

            return list;
        }

        /// <summary>
        /// Kauft ein bestimmtes Objekt
        /// </summary>
        /// <param name="id">ID des Objekts</param>
        /// <returns></returns>
        public bool BuyItem(String id)
        {
            String uri = "/shop/buy?id=" + id;
            var request = CreateWebRequest(uri, true);

            // ----------------------------
            // OK: <response>ok</response>
            // ----------------------------

            XElement response = ProcessXMLWebRequest(request);

            return response.Name.LocalName.ToLower() == "response"
                && response.Value.ToUpper() == "OK";
        }

        /// <summary>
        /// Lädt ein Objekt herunter
        /// </summary>
        /// <param name="id">ID des Objekts</param>
        /// <returns>Stream zum Objekt</returns>
        public DownloadInfo DownloadItem(String id)
        {
            //return DataClasses.Music.getTestStream();
            //return new System.IO.FileStream("D:\\Test.wav", System.IO.FileMode.Open);

            String uri = "/shop/download?id=" + id;
            var request = CreateWebRequest(uri, true);

            HttpWebResponse response = (HttpWebResponse)request.GetResponse();

            // Statuscode überprüfen, ob Fehler
            if (response.StatusCode != HttpStatusCode.OK)
                throw new Exception("Statuscode: " + response.StatusCode.ToString());

            // Kein Content?
            if (response.ContentLength == 0)
                throw new Exception("Not allowed to download.");

            // Downloadinfo bauen
            var downloadInfo = new DownloadInfo()
            {
                ContentLength = response.ContentLength,
                ContentStream = response.GetResponseStream()
            };

            return downloadInfo;
        }
    }
}
